"""
贪吃蛇游戏

1. 绘制了背景、网格
2. 绘制了蛇身、食物
3. 处理用户按键输入
4. 处理蛇吃食物
5. 处理画面fps，速度更新
6. 其他业务逻辑
    墙体碰撞，game_over √
    从屏幕另一端出现 √
    碰到身体，game_over √
    分数高了，游戏要变快 √
    食物不能出现在身体上 √

"""
import pygame

from models import *


class Game:

    def __init__(self, title):

        # 游戏初始化
        pygame.init()
        # 设置窗口大小，得到窗口对象
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        # 设置标题
        pygame.display.set_caption(title)
        # 设置icon
        pygame.display.set_icon(pygame.image.load("img/icon.png"))
        # 加载一次背景图(缩放)
        bg_image = pygame.image.load("img/bg.png")
        self.bg_image = pygame.transform.scale(bg_image, (SCREEN_WIDTH, SCREEN_HEIGHT))

        # 根据屏幕的宽高和BLOCK_SIZE大小生成所有网格的坐标列表
        x_range = SCREEN_WIDTH // BLOCK_SIZE  # 640 / 20 = 32
        y_range = SCREEN_HEIGHT // BLOCK_SIZE  # 480 / 20 = 24

        self.all_grid_position: list[(int, int)] = [(x, y) for x in range(x_range) for y in range(y_range)]

    def gen_food_position(self, snake: Snake):
        """
        根据蛇身体，随机生成食物坐标
        :param snake:
        :return: x,y食物的坐标
        """
        # 将当前snake的所有坐标放到一个列表中
        snake_body = [(node.x // BLOCK_SIZE, node.y // BLOCK_SIZE) for node in snake.snake_body]
        # 将所有网格坐标复制一份，并将snake的坐标去掉
        all_grid_position = self.all_grid_position[:]
        for node in snake_body:
            if node not in all_grid_position:
                continue
            all_grid_position.remove(node)

        # 从剩余的坐标中随机取一个
        return random.choice(all_grid_position)

    def start(self):
        snake = Snake(2, 2)
        food = Food(self.gen_food_position(snake))

        clock = pygame.time.Clock()
        # 是否游戏结束
        is_game_over = False
        # 是否游戏胜利
        is_game_win = False

        # 画面更新的时间间隔，初始值为0.2秒, 等同于速度speed = 1 / 0.2 = 5格/s
        frame_update_period = 0.2
        # 画面更新的最小时间间隔，初始值为0.1秒, 等同于速度speed = 1 / 0.1 = 10格/s
        min_period = 0.1
        # 方向长按计数器
        direction_count_dict = {
            pygame.K_DOWN: 0,
            pygame.K_RIGHT: 0,
            pygame.K_UP: 0,
            pygame.K_LEFT: 0
        }

        last_time = time.time()

        # new_direction = None
        while True:
            # 1.处理事件--------------------------------------------------------
            # 两帧之间，用户可能会按下多个按钮（比如快速的按 上、下）
            event_list = pygame.event.get()
            for event in event_list:
                if event.type == pygame.QUIT:
                    # 退出游戏
                    self.exit_game()
                if event.type == pygame.KEYDOWN:
                    # 键盘按下，注意这里需要切换到英文输入法
                    if event.key == pygame.K_q:  # key q
                        self.exit_game()
                    elif event.key == pygame.K_SPACE:
                        if is_game_over or is_game_win:
                            # 重启游戏依靠外部循环
                            return

                    snake.handle_key_event(event)

            # 速度增益比例
            speed_increase = 1.0
            # 如果用户在前进方向长按，则增加100%的速度增益
            pressed = pygame.key.get_pressed()
            if pressed[snake.direction]:
                if direction_count_dict[snake.direction] < 50:
                    direction_count_dict[snake.direction] += 1
                else:
                    speed_increase = 2.0
            else:
                direction_count_dict[snake.direction] = 0

            # 2.游戏事件--------------------------------------------------------
            cur_time = time.time()
            period = (frame_update_period * (1.0 / speed_increase))
            if not is_game_over \
                    and not is_game_win \
                    and cur_time - last_time > period:
                # 0.2秒更新一次画面，随着分数越高，更新越快
                last_time = cur_time

                new_dir = snake.get_current_direction()
                if new_dir:
                    snake.update_direction(new_dir)

                snake.move()

                # 判断蛇是否吃到了食物
                if snake.snake_body[0] == food.rect:
                    # 蛇的长一节
                    snake.grow()

                    # 吃到食物，刷新食物
                    food = Food(self.gen_food_position(snake))

                    # 如果蛇身的长度等于所有网格的数量，说明蛇吃满了，游戏胜利
                    if len(snake.snake_body) == len(self.all_grid_position):
                        is_game_win = True

                    if frame_update_period > min_period:
                        # 速度加快
                        frame_update_period *= 0.9

                # 判断蛇身是否越界（四周边界）
                snake_head = snake.snake_body[0]
                if snake_head.x < 0 or snake_head.x >= SCREEN_WIDTH or snake_head.y < 0 or snake_head.y >= SCREEN_HEIGHT:
                    is_game_over = True

                # 判断蛇头是否碰到了蛇身
                if snake_head in snake.snake_body[1:]:
                    is_game_over = True

            # 3.绘制画面--------------------------------------------------------
            # self.screen.fill((0, 255, 0))
            # 绘制背景图
            self.screen.blit(self.bg_image, (0, 0))
            # 绘制网格
            # 绘制所有横线 h = 640 // 20 = 24  [0, 20, 40, 60, ... 640)
            for y in range(0, SCREEN_HEIGHT, BLOCK_SIZE):
                pygame.draw.line(self.screen, COLOR_GRAY, (0, y), (SCREEN_WIDTH, y))
            # 绘制所有竖线 v = 480 // 20 = 24  [0, 20, 40, 60, ... 480)
            for x in range(0, SCREEN_WIDTH, BLOCK_SIZE):
                pygame.draw.line(self.screen, COLOR_GRAY, (x, 0), (x, SCREEN_HEIGHT))

            # 绘制蛇身蛇头
            snake.draw(self.screen)
            # 绘制食物
            food.draw(self.screen)

            fps = clock.get_fps()  # 获取真实每秒帧率
            # 将fps绘制到屏幕右上角
            self.show_text("FPS: {:.2f}".format(fps), 20, SCREEN_WIDTH - 100, 10)
            # 将snake的score绘制到屏幕左上角
            self.show_text("Score: {}".format(snake.score), 20, 10, 10)
            # 将speed绘制到屏幕左上角
            speed = 1 / period  # 速度，每秒移动的格子数
            self.show_text("Speed: {:.2f}".format(speed), 20, 10, 30)

            if is_game_over:
                # 显示Game Over, 得分, 按任意键重新开始, 按Q退出游戏
                self.show_text("游戏结束", 40, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4)
                self.show_text("得分: {}".format(snake.score), 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 50)
                self.show_text("按空格键重新开始", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 100)
                self.show_text("按Q退出游戏", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 150)
            elif is_game_win:
                # 显示恭喜你
                self.show_text("恭喜你，通关了！", 40, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4)
                self.show_text("按空格键重新开始", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 50)
                self.show_text("按Q退出游戏", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 100)
            # 4.刷新-----------------------------------------------------------
            # 刷新整个屏幕、窗体
            # pygame.display.flip()
            # 刷新指定区域（效率更高）参数不传时同flip
            pygame.display.update()

            # 5.控制节奏---------------------------------------------------------
            clock.tick(100)  # 设置游戏节奏为100（每秒刷新100次）作用等同于time.sleep,但是更精确，因为每次绘制的时间不一样

    def show_text(self, text, font_size, x, y):
        font = pygame.font.SysFont("SimHei", font_size)
        text = font.render(text, True, COLOR_RED)
        self.screen.blit(text, (x, y))

    @staticmethod
    def exit_game():
        pygame.display.quit()
        sys.exit()


if __name__ == '__main__':
    game = Game("贪吃蛇v2.0")
    while True:
        game.start()
